package edu.mercer.sse.ldap;

import edu.mercer.sse.app.Config;

import java.security.GeneralSecurityException;
import java.sql.SQLException;
import java.util.Iterator;
import java.util.Map;

import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.unboundid.ldap.sdk.LDAPException;

import edu.mercer.sse.database.DB2LDAPLayout;
import edu.mercer.sse.errors.CriticalException;
import edu.mercer.sse.errors.CustomLogger;
import edu.mercer.sse.errors.ErrorLoggingVisitor;
import edu.mercer.sse.errors.LoggerCustom;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;

/**
 * Show Database entries that exist and require modifying in LDAP.<p>
 * 
 * Input: <b>Database entry</b>. Compared to LDAP entries. <b>Do not</b> 
 * 	provide LDAP entries to this Predicate; i.e., is one-way comparison.
 *  <p>
 *  * Report which Database entries are present in LDAP and vary from 
 *  database, as in database->LDAP layout data synchronization/publishing.<p>
 *
 * @see Predicate
 */

public class LDAPRevisePredicate implements 

		Predicate<Map<String,String>>
{
	private static final CustomLogger _log = LoggerCustom.obtainLog( 
			
			"LDAPRevisePredicate", 
				
			Config.getLogLevel());	
	
	/**
	 * Show Database entries that exist and require modifying in LDAP.<p>
	 * 
	 * No-arg constructor.<p>
	 *  
	 * Input: <b>Database entry</b>. Compared to LDAP entries. <b>Do not</b> 
	 * 	provide LDAP entries to this Predicate; i.e., is one-way comparison.
	 *  <p>
	 *  * Report which Database entries are present in LDAP and vary from 
	 *  database, as in database->LDAP layout data synchronization/publishing.<p>
	 *
	 * @see Predicate
	 */
	
	public LDAPRevisePredicate(){}
	
	
	@Override
	public boolean apply( final Map<String, String> databaseEntry )
	{
		try
		{
			checkNotNull( databaseEntry, "null database entry, expect " + 
					"ImmutableMap<String, String>" );

			checkArgument( databaseEntry.size() > 0, "empty ImmutableMap " +
					"database entry" );
									
			final ImmutableList<ImmutableMap<String,String>> ldapEntries = 
					LDAPOrgUnit.getList();

			final String uidKey = Config.settings().getProperty(
					"ldap-member-id");

			checkArgument( ldapEntries.size() > 0, "zero LDAP OU entries " + 
					"retrieved" );
			
			String userIDfromDB = "";
			
			Boolean dbColumnUsage = false;
			Boolean ldapKeyUsage  = false;
			
			if( databaseEntry.containsKey( uidKey ) )
			{ 			
				userIDfromDB = databaseEntry.get( uidKey );

				ldapKeyUsage = true;
			
			}
			else
			{
				/* 
				 * uid missing, seek query columns instead of ldap keys 
				 */
				
				final String uidQryColumn = DB2LDAPLayout
						.getLayout()
						.inverse()
						.get( uidKey );
				
				checkArgument( databaseEntry.containsKey( uidQryColumn ), 
						"missing " 
						+ uidKey 
						+ " and " 
						+ uidQryColumn );
			
				userIDfromDB = databaseEntry.get( uidQryColumn );

				dbColumnUsage = true;
				
			}//if
				
			checkArgument( userIDfromDB.length() > 0, "empty: " + uidKey 
					+ " in entry from database" );

			final Iterator<ImmutableMap<String,String>> ldapRecs = 
					ldapEntries.iterator(); 
			
			checkArgument( ldapRecs.hasNext(), "fail to iterate ldap" 
					+ " entries" );
			
			while( ldapRecs.hasNext() )
			{
				ImmutableMap<String,String> ldapEntry = ldapRecs.next();
				
				/* 
				 * located user in ldap record and corresponding database 
				 * record 
				 * 
				 * will not report missing users, use LDAPAdditions
				 */
				
				if( ldapEntry.get( uidKey ).equals( userIDfromDB ) )
				{
					/*
					 * iterate and compare each ldap attribute
					 */
					
					for( String ldapKey : ldapEntry.keySet() )
					{
						String dbKey = "";

						/*
						 * determine database entry map structure: if ldap key 
						 * 	or query column used
						 *  
						 * allow uid, cn, sn...OR user_id, first, last... 
						 */
						
						if( ldapKeyUsage ) dbKey = ldapKey;
						
						if( dbColumnUsage ) dbKey = DB2LDAPLayout
								.getLayout()
								.inverse()
								.get( ldapKey );						
						
						checkNotNull( dbKey, "null database entry key, " 
								+ "failed to determine map structure if "
								+ "ldap key or db column used" );
						
						/* attribute varies from ldap, needs updating */
						if( databaseEntry.containsKey( dbKey   ) &&
								ldapEntry.containsKey( ldapKey ) &&
								
							  databaseEntry.get( dbKey ).equals(
								  ldapEntry.get(ldapKey)))
						{
							;	// attributes are equal
							
						}
						else if( databaseEntry.containsKey( dbKey   ) &&
								     ldapEntry.containsKey( ldapKey ) &&
								
							! databaseEntry.get( dbKey ).equals(
								  ldapEntry.get(ldapKey)))
						{
							_log.fine( dbKey + " != " + ldapKey );
							
							return true;

						}
						/* attribute not in ldap, needs adding */

						else if( databaseEntry.containsKey( dbKey ) )
						{
							_log.fine( ldapKey + " not in entry: " 
									+ ldapEntry );
							
							return true;

						}//if
					}//for
				}//if
				
			}//while
			
		} 
		catch( final LDAPException			  |
					 GeneralSecurityException |
					 SQLException err )
		{
			final CriticalException errWrapped = new CriticalException( err );
			errWrapped.accept( new ErrorLoggingVisitor() );
			
			return false;
			
		}//try

		return false;
		
	}//method	
	
	
}//class
